/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.transform.ii;

import boofcv.struct.image.ImageFloat32;


/**
 * @author Peter Abeles
 */
public class DerivativeIntegralImage {

	/**
	 * Creates a kernel for a symmetric box derivative.
	 *
	 * @param r Radius of the box.  width is 2*r+1
	 * @return Kernel Kernel for derivative.
	 */
	public static IntegralKernel kernelDerivX( int r , IntegralKernel ret ) {
		if( ret == null )
			ret = new IntegralKernel(2);

		ret.blocks[0].set(-r-1,-r-1,-1,r);
		ret.blocks[1].set(0,-r-1,r,r);
		ret.scales[0] = -1;
		ret.scales[1] = 1;

		return ret;
	}

	/**
	 * Creates a kernel for a symmetric box derivative.
	 *
	 * @param r Radius of the box.  width is 2*r+1
	 * @return Kernel Kernel for derivative.
	 */
	public static IntegralKernel kernelDerivY( int r , IntegralKernel ret ) {
		if( ret == null )
			ret = new IntegralKernel(2);

		ret.blocks[0].set(-r-1,-r-1,r,-1);
		ret.blocks[1].set(-r-1,0,r,r);
		ret.scales[0] = -1;
		ret.scales[1] = 1;

		return ret;
	}


	/**
	 * Creates a kernel for the Haar wavelet "centered" around the target pixel.
	 *
	 * @param r Radius of the box.  width is 2*r
	 * @return Kernel for a Haar x-axis wavelet.
	 */
	public static IntegralKernel kernelHaarX( int r , IntegralKernel ret) {
		if( ret == null )
			ret = new IntegralKernel(2);

		ret.blocks[0].set(-r, -r, 0, r);
		ret.blocks[1].set(0,-r,r,r);
		ret.scales[0] = -1;
		ret.scales[1] = 1;

		return ret;
	}

	/**
	 * Creates a kernel for the Haar wavelet "centered" around the target pixel.
	 *
	 * @param r Radius of the box.  width is 2*r
	 * @return Kernel for a Haar y-axis wavelet.
	 */
	public static IntegralKernel kernelHaarY( int r , IntegralKernel ret) {
		if( ret == null )
			ret = new IntegralKernel(2);

		ret.blocks[0].set(-r,-r,r,0);
		ret.blocks[1].set(-r,0,r,r);
		ret.scales[0] = -1;
		ret.scales[1] = 1;

		return ret;
	}

	public static IntegralKernel kernelDerivXX( int size , IntegralKernel ret ) {
		if( ret == null )
			ret = new IntegralKernel(2);

		// lobe size
		int blockW = size/3;
		// horizontal band size
		int blockH = size-blockW-1;

		int r1 = blockW/2;
		int r2 = blockW+r1;
		int r3 = blockH/2;

		ret.blocks[0].set(-r2-1,-r3-1,r2,r3);
		ret.blocks[1].set(-r1 - 1, -r3 - 1, r1, r3);
		ret.scales[0] = 1;
		ret.scales[1] = -3;

		return ret;
	}

	public static IntegralKernel kernelDerivYY( int size , IntegralKernel ret ) {
		if( ret == null )
			ret = new IntegralKernel(2);

		int blockW = size/3;
		int blockH = size-blockW-1;

		int r1 = blockW/2;
		int r2 = blockW+r1;
		int r3 = blockH/2;

		ret.blocks[0].set(-r3-1,-r2-1,r3,r2);
		ret.blocks[1].set(-r3-1,-r1-1,r3,r1);
		ret.scales[0] = 1;
		ret.scales[1] = -3;

		return ret;
	}

	public static IntegralKernel kernelDerivXY( int size , IntegralKernel ret ) {
		if( ret == null )
			ret = new IntegralKernel(4);

		int block = size/3;

		ret.blocks[0].set(-block-1,-block-1,-1,-1);
		ret.blocks[1].set(0,-block-1,block,-1);
		ret.blocks[2].set(0, 0, block, block);
		ret.blocks[3].set(-block-1,0,-1,block);
		ret.scales[0] = 1;
		ret.scales[1] = -1;
		ret.scales[2] = 1;
		ret.scales[3] = -1;

		return ret;
	}

	public static void derivXX( ImageFloat32 input , ImageFloat32 output , int size )
	{
		int blockW = size/3;
		int blockH = size-blockW-1;
		int radiusW = size/2;
		int radiusH = blockH/2;

		int blockW2 = 2*blockW;
		int blockW3 = 3*blockW;

		int endY = input.height - radiusH;
		int endX = input.width - radiusW;

		for( int y = radiusH+1; y < endY; y++ ) {
			int indexTop = input.startIndex + (y-radiusH-1)*input.stride;
			int indexBottom = indexTop + (blockH)*input.stride;
			int indexDst = output.startIndex + y*output.stride+radiusW+1;

			for( int x = radiusW+1; x < endX; x++ , indexTop++,indexBottom++,indexDst++) {
				float sum = input.data[indexBottom+blockW3] - input.data[indexTop+blockW3] - input.data[indexBottom] + input.data[indexTop];
				sum -= 3*(input.data[indexBottom+blockW2] - input.data[indexTop+blockW2] - input.data[indexBottom+blockW] + input.data[indexTop+blockW]);

				output.data[indexDst] = sum;
			}
		}
	}

	public static void derivYY( ImageFloat32 input , ImageFloat32 output , int size )
	{
		int blockH = size/3;
		int blockW = size-blockH-1;
		int radiusH = size/2;
		int radiusW = blockW/2;

		int rowOff1 = blockH*input.stride;
		int rowOff2 = 2*rowOff1;
		int rowOff3 = 3*rowOff1;

		int endY = input.height - radiusH;
		int endX = input.width - radiusW;

		for( int y = radiusH+1; y < endY; y++ ) {
			int indexL = input.startIndex + (y-radiusH-1)*input.stride;
			int indexR = indexL + blockW;
			int indexDst = output.startIndex + y*output.stride+radiusW+1;

			for( int x = radiusW+1; x < endX; x++ , indexL++,indexR++,indexDst++) {
				float sum = input.data[indexR+rowOff3] - input.data[indexL+rowOff3] - input.data[indexR] + input.data[indexL];
				sum -= 3*(input.data[indexR+rowOff2] - input.data[indexL+rowOff2] - input.data[indexR+rowOff1] + input.data[indexL+rowOff1]);

				output.data[indexDst] = sum;
			}
		}
	}

	public static void derivXY( ImageFloat32 input , ImageFloat32 output , int size )
	{
		int block = size/3;

		int endY = input.height - block;
		int endX = input.width - block;

		for( int y = block+1; y < endY; y++ ) {
			int indexY1 = input.startIndex + (y-block-1)*input.stride;
			int indexY2 = indexY1 + block*input.stride;
			int indexY3 = indexY2 + input.stride;
			int indexY4 = indexY3 + block*input.stride;
			int indexDst = output.startIndex + y*output.stride+block+1;

			for( int x = block+1; x < endX; x++ , indexY1++,indexY2++,indexY3++,indexY4++,indexDst++) {
				int x3 = block+1;
				int x4 = x3+block;

				float sum = input.data[indexY2+block] - input.data[indexY1+block] - input.data[indexY2] + input.data[indexY1];
				sum -= input.data[indexY2+x4] - input.data[indexY1+x4] - input.data[indexY2+x3] + input.data[indexY1+x3];
				sum += input.data[indexY4+x4] - input.data[indexY3+x4] - input.data[indexY4+x3] + input.data[indexY3+x3];
				sum -= input.data[indexY4+block] - input.data[indexY3+block] - input.data[indexY4] + input.data[indexY3];

				output.data[indexDst] = sum;
			}
		}
	}
}
